#include "db_mysql.h"
#include "common.h"

const unsigned int max_clients = FD_SETSIZE -10; // макс. кол-во подключаемых клиентов
                                           // ограничено возможностями ф-ции select

static char shutdown_server = 0; // флаг завершения процесса сервера, выставляется по сигналу TERM

//--параметры командной строки при запуске---------------|
struct in_addr host = {.s_addr = INADDR_ANY}; // -h      |
unsigned short port;                          // -p      |
//другие параметры в db_mysql.h, db_mysql.cpp            |
//-------------------------------------------------------|

/**
 * Структура данных подключенного клиента
 */
typedef struct client_conn
{
    void * __prev;        // указатель на предыдущего клиента в очереди или NULL
    void * __next;        // указатель на следующего клиента в очереди или NULL
    int sockfd;           // дескриптор сокета
    char IMEI[255];       // IMEI подключившегося устройства
    struct sockaddr_in cli_addr; // адрес клиента
    char data_buf[255];   // буффер принятых данных (если AVL пакет пришел частями)
    unsigned short data_buf_len; //размер принятых данных в буфере (всё что больше - мусор)
} cli_conn;

/**
 * Описывает саму очереди клиентов неопределенного размера
 */
typedef struct clients_vector
{
    cli_conn * __first; // адрес структуры первого клиента
    cli_conn * __last;  // адрес структуры последнего клиента
    unsigned int count; // количество клиентов в очереди
} cli_v;

/**
 * @function cv_append
 * Добавляет нового клиента в очередь по указателю
 * @param c - указатель на структуру очереди клиентов
 * @param new_client - указатель на структуру добавляемого в очередб клиента
 */
void cv_append(cli_v *c, cli_conn *new_client)
{
    if(c->count == 0)
    {
        c->__first = c->__last = new_client;
        (*new_client).__prev = NULL;
        (*new_client).__next = NULL;
    }
    else
    {
        new_client->__prev = c->__last;
        new_client->__next = NULL;
        c->__last->__next = new_client;
        c->__last = new_client;
    }
    ++(c->count);
}

/**
 * @function cv_remove
 * Удаляет клиента из очереди по дескриптору сокета
 * @param c - указатлель на структуру очереди клиентов
 * @param socket_fd - дескриптор сокета
 */
void cv_remove(cli_v * c, unsigned int socket_fd)
{
    cli_conn * cur_cli = c->__first;
    while (cur_cli != NULL)
    {
        if (cur_cli->sockfd == socket_fd)
        {
            if (cur_cli->__prev != NULL) ((cli_conn*)(cur_cli->__prev))->__next = cur_cli->__next;
            if (cur_cli->__next != NULL) ((cli_conn*)(cur_cli->__next))->__prev = cur_cli->__prev;
            free_null (cur_cli);
            --(c->count);
            break;
        }
        cur_cli = (cli_conn*)(cur_cli->__next);
    }
    
}

/**
 * @function cv_clear
 * Очищает очередб клиентов
 * @param c - указатлель на структуру очереди клиентов
 */
void cv_clear(cli_v * c)
{
    cli_conn * next_cli = NULL;
    cli_conn * cur_cli = c->__first;
    while (cur_cli != NULL)
    {
        next_cli = cur_cli->__next;
        free(cur_cli);
        cur_cli = next_cli;
    }
}

/**
 * @function cv_get_max_sockfd
 * определяет максимальный дескриптор сокета в очереди клиентов
 * @param c - указатлель на структуру очереди клиентов
 */
int cv_get_max_sockfd (cli_v * c)
{
    int max_sfd = -1;
    cli_conn * cur_cli = c->__first;
    if (cur_cli != NULL) max_sfd = cur_cli->sockfd;
    while (cur_cli != NULL)
    {
        if (cur_cli->sockfd > max_sfd) max_sfd = cur_cli->sockfd;
        cur_cli = cur_cli->__next;
    }
    return max_sfd;
}

/**
 * @function signal_TERM
 * Функция обработки сигнала TERM от ОС
 */
void signal_TERM(int sig)
{
    switch(sig)
    {
        case SIGTERM:
            log_message(LOG_FILE,"NOTE: terminate signal catched.");
            shutdown_server = 1;
            stop_db_thread();
            exit(0); //TODO корректное завершение сервера
            break;
    }
};

/**
 * @function demonize 
 * Демонизация процесса.
 * После вызова этой функции процесс начинает работать в фоне как демон.
 * Закрывает все открытые дескрипторы.
 */
void daemonize()
{
    int i, lfp;
    char str[10];
    if(getppid()==1) return; /* already a daemon */
    i=fork();
    if (i<0)
    {
        printf("fork error\n"); 
        exit(1); /* fork error */
    }
    if (i>0)
    {
        //printf("parent exits\n");
        exit(0); /* parent exits */
    }
    /* child (daemon) continues */
    setsid(); /* obtain a new process group */
    for (i=getdtablesize(); i>=0; --i) close(i); /* close all descriptors */
    i=open("/dev/null",O_RDWR);
    dup(i);
    dup(i); /* handle standart I/O */
    umask(027); /* set newly created file permissions */
    chdir(RUNNING_DIR); /* change running directory */
    lfp=open(LOCK_FILE,O_RDWR|O_CREAT,0640);
    if (lfp<0) 
    {
        log_message (LOG_FILE, "cant open lock file");
        exit(1); /* can not open */
    }
    if (lockf(lfp,F_TLOCK,0)<0) 
    {
        log_message (LOG_FILE, "Server alredy running");
        exit(0); /* can not lock */
    }
    /* first instance continues */
    sprintf(str,"%d\n",getpid());
    log_message (LOG_FILE, str);
    write(lfp,str,strlen(str)); /* record pid to lockfile */
    signal(SIGCHLD,SIG_IGN); /* ignore child */
    signal(SIGTSTP,SIG_IGN); /* ignore tty signals */
    signal(SIGTTOU,SIG_IGN);
    signal(SIGTTIN,SIG_IGN);
    signal(SIGHUP,SIG_IGN); /* ingnore hangup signal, config reload not used */ 
    signal(SIGTERM,signal_TERM); /* catch kill signal */
};


/**
 * @function proc_msg
 * Обработка поступившего сообщения от клиента.
 * Запускается как только на сокете появились данные.
 * @param sockfd - дескриптор сокета.
 * @param inc_buf - указатель на принятые данные.
 * @param inc_len - длинна (размер в байтах) принятых данных.
 */
void proc_msg(int sockfd, void *inc_buf, size_t inc_len)
{
    send(sockfd, inc_buf, inc_len, 0);
}

void proc_msg_cc(cli_conn * cc, void *inc_buf, size_t inc_lien)
{
    /*
    uint16_t imei_size;
    if (inc_len <= 4) return;
    uint32_t type_signe;
    memcpy(&type_signe, inc_buf, 4);
    if (type_sign <> 0)
    */
}

/**
 * @function parce_opt
 * Функция обработки аргументов командной строки и инициализации 
 * соответствующих им переменных.
 * !!! Должна вызываться до демонизации процесса.
 * Параметры аналогичны функции main.
 */
void parse_opt(int argc, char *argv[])
{

    port = htons(3425);

    int c;

    while (1) {
        //int this_option_optind = optind ? optind : 1;
        int option_index = 0;
        static struct option long_options[] = {
            {"db-host", 1, 0, 1},
            {"db-port", 1, 0, 2},
            {"db-user", 1, 0, 3},
            {"db-passwd", 1, 0, 4},
            {"db-name", 1, 0, 5},
            {0, 0, 0, 0}
        };

        c = getopt_long (argc, argv, "h:p:", long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
        case 1: //db-host
            printf ("параметр db-host со значением `%s'\n", optarg);
            free_null(db_host);
            db_host = (char*)malloc(strlen(optarg) + 1);
            strcpy(optarg, db_host);
            break;

        case 2: //db-port
            printf ("параметр db-port со значением `%s'\n", optarg);
            db_port = atoi(optarg);
            break;

        case 3: //db-user
            printf ("параметр db-user со значением `%s'\n", optarg);
            free_null(db_user);
            db_user = (char*)malloc(strlen(optarg) + 1);
            strcpy(optarg, db_user);
            break;

        case 4: //db-passwd
            printf ("параметр db-passwd со значением `%s'\n", optarg);
            free_null(db_pass);
            db_pass = (char*)malloc(strlen(optarg) + 1);
            strcpy(optarg, db_pass);
            break;
        
        case 5: //db-name
            printf ("параметр db-name со значением `%s'\n", optarg);
            free_null(db_name);
            db_name = (char *)malloc(strlen(optarg) + 1);
            strcpy(optarg, db_name);
            break;

        case 'h':
            printf ("параметр h со значением `%s'\n", optarg);
            if (inet_aton(optarg, &host) == -1)
            {
                printf ("Wrong ip address with -h param\n");
                exit(0);
            }
            break;

        case 'p':
            printf ("параметр p со значением `%s'\n", optarg);
            port = htons(atoi(optarg));
            break;

        case '?':
            break;

        default:
            printf ("?? getopt возвратило код символа 0%o ??\n", c);
        }
    }

    if (optind < argc) {
        printf ("элементы ARGV, не параметры: ");
        while (optind < argc)
            printf ("%s ", argv[optind++]);
        printf ("\n");
    }


}

int main(int argc,char *argv[])
{
    //обработка аргументов командной строки
    /* функциональность запуска и остановки сервера перенесена на внешнюю утилиту
    if (argc > 1) {
        int fd, len;
        pid_t pid;
        char pid_buf[16];
        if ((fd = open(LOCK_FILE, O_RDONLY)) < 0)
        {
            printf("Lock file not found. May be the server is not running?");
            exit(fd);
        }
        len = read(fd, pid_buf, 16);
        pid_buf[len] = 0;
        pid = atoi(pid_buf);
        if(!strcmp(argv[1], "stop")) 
        {
            kill(pid, SIGTERM);
            exit(EXIT_SUCCESS);
        }
        if(!strcmp(argv[1], "restart")) 
        {
            kill(pid, SIGHUP);
            exit(EXIT_SUCCESS);
        }
        printf ("usage %s [stop|restart]\n", argv[0]);
        exit (EXIT_FAILURE);
    }
    */
    parse_opt(argc, argv);
    
    //запуспить программу как демона
    daemonize();

    //запуск потока работы с БД и подключение к БД
    start_db_thread();

    int listener;
    struct sockaddr_in addr;
    char buf[1024];
    int bytes_read;

    listener = socket(AF_INET, SOCK_STREAM, 0);
    if(listener < 0)
    {
        log_message(LOG_FILE, "FATAL: listen socket open error.");
        exit(1);
    }

    fcntl(listener, F_SETFL, O_NONBLOCK);

    addr.sin_family = AF_INET;
    addr.sin_port = port;
    addr.sin_addr = host;
    if(bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        log_message(LOG_FILE, "FATAL: listen socket bind error.");
        exit(2);
    }

    listen(listener, 2);

    cli_v clients = {NULL, NULL, 0};
    fd_set allset, readset;
    FD_ZERO(&allset);
    FD_SET(listener, &allset);
   
    int max_sfd = listener;

    while(1)
    {
        // Заполняем множество сокетов
        readset = allset;

        // Задаём таймаут
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        // Ждём события в одном из сокетов
        int sel_res = select(max_sfd+1, &readset, NULL, NULL, &timeout);
        if(sel_res < 0)
        {
            log_message(LOG_FILE, "WARNING: select error occured.");
        }
        else if (sel_res == 0)
        {
            log_message(LOG_FILE, "DEBUG: select timeout.");
        }
        else
        {
            // Определяем тип события и выполняем соответствующие действия
            if( FD_ISSET(listener, &readset))
            {
                // Поступил новый запрос на соединение, используем accept
                int sock = accept(listener, NULL, NULL);
                if(sock < 0)
                {
                    log_message(LOG_FILE, "WARNING: accept error occured.");
                }
                else
                {
                    if (clients.count +1 > max_clients)
                    {
                        char str[100];
                        sprintf (str, "WARNING: can't accept connection:\
                                      to many clients connected, limit is %d.", 
                                      max_clients);
                        log_message(LOG_FILE, str);
                    }
                    else
                    {
                        //accepting connection
                        log_message(LOG_FILE, "NOTE: new connection accepted.");
                        fcntl(sock, F_SETFL, O_NONBLOCK);
                        cli_conn * new_cli;
                        new_cli = (cli_conn*)malloc(sizeof(cli_conn));
                        new_cli->sockfd = sock;
                        cv_append(&clients, new_cli);
                        FD_SET(sock, &allset);
                    }
                }
            }

            // цикл прохода по очереди клиентов
            cli_conn * cur_cli = clients.__first;
            while (cur_cli != NULL) 
            {
                if(FD_ISSET(cur_cli->sockfd, &readset))
                {
                    // Поступили данные от клиента, читаем их
                    bytes_read = recv(cur_cli->sockfd, buf, 1024, 0);
                    if(bytes_read <= 0) //клмент прислал FIN
                    {
                        log_message(LOG_FILE, "NOTE: Connection closed by client.");
                        // Соединение разорвано, удаляем сокет из множества
                        close(cur_cli->sockfd);
                        FD_CLR(cur_cli->sockfd, &allset);
                        // удаляем клиента из "вектора";
                        if (cur_cli->__prev != NULL) ((cli_conn*)(cur_cli->__prev))->__next = cur_cli->__next;
                        if (cur_cli->__next != NULL) ((cli_conn*)(cur_cli->__next))->__prev = cur_cli->__prev;
                        cli_conn * del_cli = cur_cli;
                        cur_cli = cur_cli->__next;
                        free (del_cli);
                        --(clients.count);
                        continue;
                    }

                    // Обработываем сообщение от клиента
                    proc_msg(cur_cli->sockfd, buf, bytes_read);
                } 
                cur_cli = cur_cli->__next;
            }
            // определяем новый максимальный дескриптор для select
            max_sfd = cv_get_max_sockfd(&clients);
        }
    }

    cv_clear(&clients);
    return 0;
}

